﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace Qreed.Windows.Forms.Hook
{
    public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

    public class HookEventArgs : EventArgs
    {
        public HookEventArgs(int code, IntPtr w, IntPtr l)
        {
            Code = code;
            wParam = w;
            lParam = l;
        }

        public int Code;
        public IntPtr wParam;
        public IntPtr lParam;
    }

   

    public class LocalHook : IDisposable
    {
        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalHook"/> class.
        /// </summary>
        /// <param name="type">The type.</param>
        public LocalHook(User32.HookType type) : this(type, null){}

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalHook"/> class.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="proc">The proc.</param>
        public LocalHook(User32.HookType type, HookProc proc)
        {
            _type = type;
            _proc = proc;

            if (_proc == null)
                _proc = new HookProc(DefaultHookProc);
        }

        #endregion

        #region Properties

        private IntPtr _handle = IntPtr.Zero;
        /// <summary>
        /// Gets or sets the handle.
        /// </summary>
        /// <value>The handle.</value>
        protected IntPtr Handle
        {
            get { return _handle; }
            set { _handle = value; }
        }

        private HookProc _proc;
        /// <summary>
        /// Gets the proc.
        /// </summary>
        /// <value>The proc.</value>
        protected HookProc Proc
        {
            get { return _proc; }
        }

        private User32.HookType _type;
        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <value>The type.</value>
        protected User32.HookType Type
        {
            get { return _type; }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the hook is invoked.
        /// </summary>
        public event EventHandler<HookEventArgs> Invoked;

        /// <summary>
        /// Raises the <see cref="E:Invoked"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Qreed.Windows.Forms.HookEventArgs"/> instance containing the event data.</param>
        protected virtual void OnInvoked(HookEventArgs e)
        {
            if (Invoked != null)
                Invoked(this, e);
        }

        #endregion

        /// <summary>
        /// The default hook proc.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The wparam.</param>
        /// <param name="lParam">The lparam.</param>
        /// <returns></returns>
        protected int DefaultHookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code >= 0)
            {
                HookEventArgs e = new HookEventArgs(code, wParam, lParam);
                OnInvoked(e);
            }

            return User32.CallNextHookEx(_handle, code, wParam, lParam);
        }

        /// <summary>
        /// Hooks this instance.
        /// </summary>
        public void Hook()
        {
            if (_handle != IntPtr.Zero)
                throw (new ApplicationException("Hook already set."));

            _handle = User32.SetWindowsHookEx(_type, _proc, IntPtr.Zero, Kernell32.GetCurrentThreadId());

            if (_handle == IntPtr.Zero)
                throw (new ApplicationException("Failed to start hook (" + Marshal.GetLastWin32Error() + ")"));
        }

        /// <summary>
        /// Uns the hook.
        /// </summary>
        public void UnHook()
        {
            if (_handle != IntPtr.Zero)
            {
                User32.UnhookWindowsHookEx(_handle);
                _handle = IntPtr.Zero;
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            UnHook();
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="LocalHook"/> is reclaimed by garbage collection.
        /// </summary>
        ~LocalHook()
        {
            Dispose(false);
        }

        #endregion
    }
}
